package jmine.tec.environment.db;

import java.io.IOException;

import jmine.tec.environment.utils.SpringConfigFactory;
import jmine.tec.environment.utils.TestDBApplicationContext;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.BeanFactory;

import bancosys.tec.utils.db.descriptor.FlatReferenceDatabaseBuilder.ReferenceDatabaseDescriptionType;
import bancosys.tec.utils.loader.ResourceLoader;
import bancosys.tec.utils.log.LogManager;

/**
 * Singleton que armazena uma instância de DBEnvironment a ser compartilhada entre testes.
 * 
 * @author lundberg
 */
public final class DBEnvironmentHolder {

    // Nome do arquivo do spring.
    // Ex.: "jmine-tec-environment-test.xml"
    public static final String SPRING_MAIN_XML = "test.spring.main.xml";

    // Arquivos de base de referência a serem carregados, separados por vírgula eespaços
    // Ex.: environment-test-db.xml, hydra-db.xml
    public static final String REFDB_XML = "test.refdb.xml";

    private static final DBEnvironmentHolder HOLDER = new DBEnvironmentHolder();

    private DBEnvironment environment = null;

    private String previousSpring = null;

    private String previousRefdb = null;

    /**
     * Construtor
     */
    private DBEnvironmentHolder() {
        this.environment = null;
    }

    /**
     * Obtém a instância do holder
     * 
     * @return holder
     */
    public static DBEnvironmentHolder getInstance() {
        return HOLDER;
    }

    /**
     * Instancia o ambiente de banco de dados de acordo com propriedades definidas do sistema.
     * 
     * @param spring spring
     * @param refdb refdb
     * @return DBEnvironment
     */
    public synchronized DBEnvironment instantiate(String spring, String refdb) {
        this.assertEnvironmentHasntChanged(spring, refdb);
        if (this.environment != null) {
            return this.environment;
        }
        try {
            new LogManager(new ResourceLoader(), "jmine-tec-test-log.properties", true);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        SpringConfigFactory configFactory = new SpringConfigFactory();
        BeanFactory bf = new TestDBApplicationContext(spring, configFactory.getApplicationContext()).getBeanFactory();
        this.environment =
                new DBEnvironment(bf, spring, configFactory.getDatabaseServerType(), this.getResourcesBaseReferencia(refdb),
                        ReferenceDatabaseDescriptionType.SCHEMA);
        this.environment.start();
        return this.environment;
    }

    /**
     * Assegura que os parâmetros spring e refdb não diferem de chamadas anteriores.
     * 
     * @param spring spring
     * @param refdb refdb
     */
    private void assertEnvironmentHasntChanged(String spring, String refdb) {
        if (this.previousSpring != null && !this.previousSpring.equals(spring)) {
            throw new IllegalArgumentException(String.format(
                    "Teste pede um ambiente diferente do que já foi carregado. Spring carregado %s, requerido %s.", this.previousSpring,
                    spring));
        } else {
            this.previousSpring = spring;
        }
        if (this.previousRefdb != null && !this.previousRefdb.equals(refdb)) {
            throw new IllegalArgumentException(String.format(
                    "Teste pede um ambiente diferente do que já foi carregado. Refdb carregado %s, requerido %s.", this.previousRefdb,
                    refdb));
        } else {
            this.previousRefdb = refdb;
        }
    }

    /**
     * Obtém os nomes dos arquivos XML de refdb.
     * 
     * @param refdb refdb
     * @return arquivos xml
     */
    private String[] getResourcesBaseReferencia(String refdb) {
        return StringUtils.isBlank(refdb) ? new String[0] : refdb.split("\\s,\\s");
    }

    /**
     * @return the environment
     */
    public DBEnvironment getEnvironment() {
        return this.environment;
    }

    /**
     * Define o ambiente de testes.
     * 
     * @param environment environment
     */
    public void setEnvironment(DBEnvironment environment) {
        this.environment = environment;
    }
}
